/*----------------------------------------------------------------------
 *
 * tableamapi.c
 *		Postgres 表访问方法的 API 支持例程
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * src/backend/access/table/tableamapi.c
 *----------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/heapam.h"
#include "access/htup_details.h"
#include "access/tableam.h"
#include "access/xact.h"
#include "catalog/pg_am.h"
#include "catalog/pg_proc.h"
#include "commands/defrem.h"
#include "miscadmin.h"
#include "utils/fmgroids.h"
#include "utils/memutils.h"
#include "utils/syscache.h"


/*
 * GetTableAmRoutine
 *		调用指定的访问方法处理程序例程以获取其
 *		TableAmRoutine 结构，该结构将在调用者的
 *		内存上下文中进行 palloc。
 */
const TableAmRoutine *
GetTableAmRoutine(Oid fc_amhandler)
{
	Datum		fc_datum;
	const TableAmRoutine *fc_routine;

	fc_datum = OidFunctionCall0(fc_amhandler);
	fc_routine = (TableAmRoutine *) DatumGetPointer(fc_datum);

	if (fc_routine == NULL || !IsA(fc_routine, TableAmRoutine))
		elog(ERROR, "table access method handler %u did not return a TableAmRoutine struct",
			 fc_amhandler);

	/*
	 * 断言所有必需的回调都存在。这使得保持 AM 更新更为容易，
	 * 例如，当将它们向前移植到新的主要版本时。
	 */
	Assert(fc_routine->scan_begin != NULL);
	Assert(fc_routine->scan_end != NULL);
	Assert(fc_routine->scan_rescan != NULL);
	Assert(fc_routine->scan_getnextslot != NULL);

	Assert(fc_routine->parallelscan_estimate != NULL);
	Assert(fc_routine->parallelscan_initialize != NULL);
	Assert(fc_routine->parallelscan_reinitialize != NULL);

	Assert(fc_routine->index_fetch_begin != NULL);
	Assert(fc_routine->index_fetch_reset != NULL);
	Assert(fc_routine->index_fetch_end != NULL);
	Assert(fc_routine->index_fetch_tuple != NULL);

	Assert(fc_routine->tuple_fetch_row_version != NULL);
	Assert(fc_routine->tuple_tid_valid != NULL);
	Assert(fc_routine->tuple_get_latest_tid != NULL);
	Assert(fc_routine->tuple_satisfies_snapshot != NULL);
	Assert(fc_routine->index_delete_tuples != NULL);

	Assert(fc_routine->tuple_insert != NULL);

	/*
	 * 可以选用，但需要在解析分析期间抛出错误。
	 */
	Assert(fc_routine->tuple_insert_speculative != NULL);
	Assert(fc_routine->tuple_complete_speculative != NULL);

	Assert(fc_routine->multi_insert != NULL);
	Assert(fc_routine->tuple_delete != NULL);
	Assert(fc_routine->tuple_update != NULL);
	Assert(fc_routine->tuple_lock != NULL);

	Assert(fc_routine->relation_set_new_filenode != NULL);
	Assert(fc_routine->relation_nontransactional_truncate != NULL);
	Assert(fc_routine->relation_copy_data != NULL);
	Assert(fc_routine->relation_copy_for_cluster != NULL);
	Assert(fc_routine->relation_vacuum != NULL);
	Assert(fc_routine->scan_analyze_next_block != NULL);
	Assert(fc_routine->scan_analyze_next_tuple != NULL);
	Assert(fc_routine->index_build_range_scan != NULL);
	Assert(fc_routine->index_validate_scan != NULL);

	Assert(fc_routine->relation_size != NULL);
	Assert(fc_routine->relation_needs_toast_table != NULL);

	Assert(fc_routine->relation_estimate_size != NULL);

	/* 可选，但一个回调暗示另一个的存在 */
	Assert((fc_routine->scan_bitmap_next_block == NULL) ==
		   (fc_routine->scan_bitmap_next_tuple == NULL));
	Assert(fc_routine->scan_sample_next_block != NULL);
	Assert(fc_routine->scan_sample_next_tuple != NULL);

	return fc_routine;
}

/* check_hook: 验证新的 default_table_access_method */
bool check_default_table_access_method(char **fc_newval, void **fc_extra, GucSource fc_source)
{
	if (**fc_newval == '\0')
	{
		GUC_check_errdetail("%s cannot be empty.",
							"default_table_access_method");
		return false;
	}

	if (strlen(*fc_newval) >= NAMEDATALEN)
	{
		GUC_check_errdetail("%s is too long (maximum %d characters).",
							"default_table_access_method", NAMEDATALEN - 1);
		return false;
	}

	/*
	 * 如果我们不在事务中，或者未连接到数据库，我们
	 * 就无法进行必要的目录访问以验证该方法。必须
	 * 对值抱有信心。
	 */
	if (IsTransactionState() && MyDatabaseId != InvalidOid)
	{
		if (!OidIsValid(get_table_am_oid(*fc_newval, true)))
		{
			/*
			 * 当 source == PGC_S_TEST 时，不要对一个
			 * 不存在的表访问方法抛出硬错误，仅发出 NOTICE。请参见
			 * guc.h 中的注释。
			 */
			if (fc_source == PGC_S_TEST)
			{
				ereport(NOTICE,
						(errcode(ERRCODE_UNDEFINED_OBJECT),
						 errmsg("table access method \"%s\" does not exist",
								*fc_newval)));
			}
			else
			{
				GUC_check_errdetail("Table access method \"%s\" does not exist.",
									*fc_newval);
				return false;
			}
		}
	}

	return true;
}
